package com.edgar.download;

import android.content.Context;
import android.util.Log;

import com.edgar.baselibrary.utils.IOUtils;
import com.edgar.baselibrary.utils.NetworkUtils;
import com.edgar.baselibrary.utils.StorageUtils;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import static com.edgar.download.Constant.TAG;

/**
 * Created by dell on 2015/9/23.
 * 1.支持断点续传
 * 2.网络无效,取消下载
 * 3.检查SD卡是否存在,不存在不下载；检查SD卡剩余容量
 * 4.更新数据库状态;
 * 5.下载通知监听者.
 * 6.支持多线程下载(未实现,等到公司项目完成后,续写篇章)
 */
public class DownloadTask implements Runnable{

    private static final int SEGMENT_THREAD = 2;  //分段线程数量
    private static final int HTTP_TEMP_REDIRECT = 307;  //重定向
    private static final int MAX_REQUEST_COUNT = 3;
    private static final int READ_TIMEOUT = 20*10*100;
    private DownloadManager mDownloadManager;
    private Download mDownload;
    private String mUrl;
    private File mLocalPath;
    private long mTotalSize;
    private long mCurrentSize;

    private Context mContext;
    private Executor mSegmentDownload = Executors.newFixedThreadPool(SEGMENT_THREAD); //限定分段线程数量为2个
    private CountDownLatch mDownloadCount = new CountDownLatch(SEGMENT_THREAD);  //线程等待.

    //0-1000
    private class DownloadException extends Exception{
        public DownloadException(Exception ex){
            super(ex);
        }

        public DownloadException(String message){
            super(message);
        }
    }

    public DownloadTask(Context context,DownloadManager manager,Download download){
        this.mContext = context;
        this.mDownloadManager = manager;
        this.mDownload = download;
        this.mCurrentSize = mDownload.getDownloadSize();
        this.mTotalSize = mDownload.getSize();
        this.mUrl = mDownload.getDownloadUrl();
        this.mLocalPath = new File(mDownload.getFilePath());
        File parentFile = mLocalPath.getParentFile();
        if(!parentFile.exists()) parentFile.mkdirs();
    }

    @Override
    public void run() {
        try {
            checkStorageState();
            checkNetworkConnectivity();
            checkUserCancelOrPause();
            mDownloadManager.onStartDownload(mDownload);
            if(mTotalSize < 0){
                requestFileLength();
            }
            createTempFile();
            mDownloadManager.onDownloadComplete(mDownload);
            Log.e(Constant.TAG, "end time:" + System.currentTimeMillis());
        } catch (DownloadException e) {
            Log.e(TAG,"error:"+e.getMessage());
            //网络异常\File异常,修改download为暂停状态.
            mDownloadManager.onDownloadFailure(mDownload);
        }
    }

    /**
     * Create download temp file
     * @throws DownloadException
     */
    private void createTempFile()throws DownloadException{
        File file = new File(mLocalPath.getAbsolutePath()+".temp");
        if(file.exists()) return;
        FileOutputStream fos = null;
        try{
            byte[] buffer = new byte[(int)mTotalSize];
            fos = new FileOutputStream(file);
            fos.write(buffer,0,buffer.length);
        } catch (IOException e){
            throw new DownloadException(e);
        } finally {
            IOUtils.closeQuietly(fos);
        }
    }

    //TODO 构建分段任务.
    private SegmentDownloadTask[] buildSegmentDownload(){
        SegmentDownloadTask[] segmentDownloadTasks = new SegmentDownloadTask[SEGMENT_THREAD];
        long size = (mTotalSize - mCurrentSize); //(10-4)=6
        int segmentSizeOne = (int) (size/2);
        segmentDownloadTasks[0] = new SegmentDownloadTask(mDownloadCount,mDownload,mCurrentSize,mTotalSize/2);
        segmentDownloadTasks[1] = new SegmentDownloadTask(mDownloadCount,mDownload,(int)size,mTotalSize);
        return segmentDownloadTasks;
    }

    private URL openURL(String url)throws DownloadException{
        try {
            return new URL(url);
        } catch (MalformedURLException e) {
            throw new DownloadException(e);
        }
    }

    private HttpURLConnection openConnection(String url)throws DownloadException{
        try {
            HttpURLConnection httpURLConnection = (HttpURLConnection) openURL(url).openConnection();
            httpURLConnection.setRequestMethod("GET");
            httpURLConnection.setReadTimeout(READ_TIMEOUT);
            httpURLConnection.setConnectTimeout(READ_TIMEOUT);
            return httpURLConnection;
        } catch (IOException e) {
            throw new DownloadException(e);
        }
    }

    /**
     * Check storage state is remove.
     * @throws DownloadException
     */
    private void checkStorageState()throws DownloadException{
        if(StorageUtils.isExternalStorageRemovable()){
            throw new DownloadException("Storage remove.");
        }
    }

    /**
     * 请求文件长度
     * @throws DownloadException
     */
    public void requestFileLength()throws DownloadException{
        int requestCount = 0;
        String url = mUrl;
        while (requestCount++ < MAX_REQUEST_COUNT){
            HttpURLConnection httpURLConnection = null;
            try {
                httpURLConnection = openConnection(mUrl);
                int responseCode = httpURLConnection.getResponseCode();
                switch (responseCode){
                    case HttpURLConnection.HTTP_OK:
                        parserHeader(httpURLConnection);
                        return;
                    case HttpURLConnection.HTTP_MOVED_PERM:
                    case HttpURLConnection.HTTP_MOVED_TEMP:
                    case HttpURLConnection.HTTP_SEE_OTHER:
                    case HTTP_TEMP_REDIRECT:
                        String location = httpURLConnection.getHeaderField("Location");
                        if(responseCode == HttpURLConnection.HTTP_MOVED_PERM){
                            //重定向301,指向新的url下载
                            url = new URL(openURL(url),location).toString();
                            this.mUrl = url;
                            mDownload.setDownloadUrl(url);
                            mDownloadManager.updateDownloadToDatabase(mDownload);
                        }
                        continue;
                }
            } catch (IOException e){
                throw new DownloadException(e);
            } finally {
                if(httpURLConnection != null){
                    httpURLConnection.disconnect();
                }
            }
        }
    }


    public void checkUserCancelOrPause()throws DownloadException{
        if(mDownload.isCancel() || mDownload.getStatus() == Constant.PAUSE){
            throw new DownloadException("");
        }
    }

    public void checkNetworkConnectivity()throws DownloadException {
        if(!NetworkUtils.isConnected(mContext)){
            throw new DownloadException("Network not connection.");
        }
    }

    /**
     * 解析请求成功的header字段
     * @param httpURLConnection
     */
    private void parserHeader(HttpURLConnection httpURLConnection){
        mTotalSize = getHeaderFieldLong(httpURLConnection,"Content-Length",0);
        mDownload.setSize(mTotalSize);
        if(mDownload.getContentType() == null){
            mDownload.setContentType(Help.normalizeMimeType(httpURLConnection.getContentType()));
        }
        mDownloadManager.updateDownloadToDatabase(mDownload);
    }

    /**
     * get header field convert long
     * @param conn
     * @param field
     * @param defaultValue
     * @return
     */
    private long getHeaderFieldLong(URLConnection conn, String field, long defaultValue) {
        try {
            return Long.parseLong(conn.getHeaderField(field));
        } catch (NumberFormatException e) {
            return defaultValue;
        }
    }

    /**
     * @param connection
     */
    private void transferData(HttpURLConnection connection)throws DownloadException {
        checkUserCancelOrPause();
        InputStream inputStream = null;
        BufferedOutputStream bos = null;
        try {
            inputStream = connection.getInputStream();
            if(!StorageUtils.isAvailable(mTotalSize)){
                throw new DownloadException("Storage not available space.");
            }
            if(mCurrentSize <= 0 && mLocalPath.exists()){
                mLocalPath.delete();
            }
            bos = new BufferedOutputStream(new FileOutputStream(mLocalPath,true));
            byte[] buffer = new byte[4096];
            int readLen = 0;
            while ((readLen = inputStream.read(buffer)) != -1){
                //正在写的途中,发现此时空间不够了.
                if(!StorageUtils.isAvailable(readLen)){
                    throw new DownloadException("Storage not available space.");
                }
                checkUserCancelOrPause();
                bos.write(buffer, 0, readLen);
                bos.flush();
                mCurrentSize = mCurrentSize + readLen;
                int downloadProgress = (int) ((float) mCurrentSize / mTotalSize * 100);
                mDownload.setStatus(Constant.DOWNLOADING);
                mDownload.setDownloadSize(mCurrentSize);
                mDownload.setProgress(downloadProgress);
                mDownloadManager.updateDownloadToDatabase(mDownload);
                checkUserCancelOrPause();
            }
        } catch (IOException e) {
            throw new DownloadException(e);
        } finally {
            long currentTimeMillis = System.currentTimeMillis();
            IOUtils.closeQuietly(bos);
            IOUtils.closeQuietly(inputStream);
            Log.e(TAG,"关闭流时间:"+(System.currentTimeMillis()-currentTimeMillis));
        }
    }

    public long getDownloadId(){
        return mDownload.getId();
    }

    @Override
    public boolean equals(Object o){
        if(o == null) return false;
        DownloadTask downloadTask = (DownloadTask)o;
        return downloadTask.getDownloadId() == getDownloadId();
    }

    private class SegmentDownloadTask implements Runnable{

        private final CountDownLatch mCountDownLatch;
        private final Download mDownload;
        private long mStartPos = 0;
        private long mEndPos = 0;

        public SegmentDownloadTask(CountDownLatch countDownLatch,Download download,long startPos,long endPos){
            this.mCountDownLatch = countDownLatch;
            this.mDownload = download;
        }

        @Override
        public void run() {
            //执行完结束
            try {
                executeDownload();
            } catch (DownloadException e) {
                e.printStackTrace();
            } finally {
                mCountDownLatch.countDown();
            }
        }

        /**
         * 执行下载
         */
        private void executeDownload()throws DownloadException {
            int requestCount = 0;
            HttpURLConnection httpURLConnection = null;
            try{
                String url = mUrl;
                while (requestCount++ < MAX_REQUEST_COUNT){
                    checkUserCancelOrPause();
                    httpURLConnection = openConnection(url);
                    addRequestHeader(httpURLConnection);
                    if(httpURLConnection.getErrorStream() != null){
                        throw new DownloadException("Download request error.");
                    }
                    int statusCode = httpURLConnection.getResponseCode();
                    switch (statusCode){
                        case HttpURLConnection.HTTP_PARTIAL:
                            //206
                            transferData(httpURLConnection);
                            return;
                        case HttpURLConnection.HTTP_MOVED_PERM:
                        case HttpURLConnection.HTTP_MOVED_TEMP:
                        case HttpURLConnection.HTTP_SEE_OTHER:
                        case HTTP_TEMP_REDIRECT:
                            String location = httpURLConnection.getHeaderField("Location");
                            if(statusCode == HttpURLConnection.HTTP_MOVED_PERM){
                                //重定向301,指向新的url下载
                                synchronized (this){
                                    url = new URL(openURL(url),location).toString();
                                    mDownload.setDownloadUrl(url);
                                    mDownloadManager.updateDownloadToDatabase(mDownload);
                                }
                            }
                            continue;
                        default:
                            throw new DownloadException("Download fail:"+mUrl);
                    }
                }
            } catch (IOException e){
                throw new DownloadException(e);
            } finally {
                long currentTimeMillis = System.currentTimeMillis();
                if(httpURLConnection != null){
                    httpURLConnection.disconnect();
                }
                Log.e(TAG,"耗时:"+(System.currentTimeMillis()-currentTimeMillis));
            }
        }

        private void addRequestHeader(HttpURLConnection connection){
            connection.addRequestProperty("Accept-Encoding", "identity");
            if(mCurrentSize > 0 && mLocalPath.exists()){
                connection.addRequestProperty("Range", String.format("bytes=%d-%d",mStartPos,mEndPos));
            } else {
                mCurrentSize = 0;
                mDownload.setDownloadSize(mCurrentSize);
            }
        }
    }
}